package com.android.bonn.fast.drawable.tags;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.text.TextPaint;
import android.text.style.LineHeightSpan;
import android.text.style.ReplacementSpan;

import java.lang.ref.WeakReference;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.res.ResourcesCompat;

public class RoundedTagSpan extends ReplacementSpan implements LineHeightSpan {
    private Context context;
    private int cornerRadius;
    private int textColor;
    private int tagTextSize;
    private int tagTextVerticalPadding;
    private int tagTextHorizontalPadding;
    private int iconTextSpacePadding;
    private int tagWhiteSpacePadding;
    private int originalTextSize;
    private WeakReference<Drawable> drawableRef;
    private WeakReference<Bitmap> leftIconBitmapRef;
    private int leftIconDrawableResource;

    //private int originalTextBoundHeight;
    private int tagTextBoundHeight;
    //private int scaleTagToTextCenter;

    private Paint.FontMetrics originalFontMetrics;
    private int chooseHeightAddTop;
    private int chooseHeightAddBottom;
    private Paint mBgPaint;

    public RoundedTagSpan(Context context,
                          int cornerRadius,
                          int backgroundColor,
                          int textColor,
                          int tagTextSizeDimenResource,
                          int tagTextVerticalPaddingDimenResource,
                          int tagTextHorizontalPaddingDimenResource,
                          int iconTextSpacePaddingDimenResource,
                          int tagWhiteSpacePaddingDimenResource,
                          int leftIconDrawableResource,
                          Paint.Style bgStyle,
                          float bgStrokeWidth) {
        super();

        this.context = context;


        setCornerRadius(cornerRadius);
        setTextColor(textColor);
        setTagTextSizeDimenResource(tagTextSizeDimenResource);
        setTagTextVerticalPaddingDimenResource(tagTextVerticalPaddingDimenResource);
        setTagTextHorizontalPaddingDimenResource(tagTextHorizontalPaddingDimenResource);
        setIconTextSpacePaddingDimenResource(iconTextSpacePaddingDimenResource);
        setTagWhiteSpacePadding(tagWhiteSpacePaddingDimenResource);
        //setOriginalTextSizeDimenResource(originalTextSize);
        setLeftIconDrawableResource(leftIconDrawableResource);

        /* Measure the height of the text, as paint.getFontMetrics().alignment will
         * have spacing on top. Calculate the halved difference of text height, to derive how
         * much tag has to move. Both tag and text start from the same baseline.
         */
        //originalTextBoundHeight = measureTextBoundHeight(originalTextSize);
        tagTextBoundHeight = measureTextBoundHeight(tagTextSize);
        //scaleTagToTextCenter = tagTextBoundHeight;// (originalTextBoundHeight - tagTextBoundHeight) / 2;

        measureChooseHeightRequired();

        mBgPaint = new Paint();
        mBgPaint.setStyle(bgStyle);
        mBgPaint.setColor(backgroundColor);
        mBgPaint.setAntiAlias(true);
        mBgPaint.setStrokeWidth(bgStrokeWidth);
    }

    private void setLeftIconDrawableResource(int leftIconDrawableResource) {
        this.leftIconDrawableResource = leftIconDrawableResource;
    }

    public void setTextColor(int textColor) {
        this.textColor = textColor;
    }

    public void setTagTextSizeDimenResource(int dimenResource) {
        setTagTextSize(dimenResource == 0 ? 0 :
                context.getResources().getDimensionPixelSize(dimenResource));
    }

    public void setTagTextSize(int tagTextSize) {
        this.tagTextSize = tagTextSize;
    }

    public void setTagTextVerticalPaddingDimenResource(int dimenResource) {
        setTagTextVerticalPadding(dimenResource == 0 ? 0 :
                context.getResources().getDimensionPixelSize(dimenResource));
    }

    public void setTagTextVerticalPadding(int tagTextVerticalPadding) {
        this.tagTextVerticalPadding = tagTextVerticalPadding;
    }

    public void setTagTextHorizontalPaddingDimenResource(int dimenResource) {
        setTagTextHorizontalPadding(dimenResource == 0 ? 0 :
                context.getResources().getDimensionPixelSize(dimenResource));
    }

    public void setTagTextHorizontalPadding(int tagTextHorizontalPadding) {
        this.tagTextHorizontalPadding = tagTextHorizontalPadding;
    }

    public void setIconTextSpacePaddingDimenResource(int dimenResource) {
        setIconTextSpacePadding(dimenResource == 0 ? 0 :
                context.getResources().getDimensionPixelSize(dimenResource));
    }

    public void setIconTextSpacePadding(int iconTextSpacePadding) {
        this.iconTextSpacePadding = iconTextSpacePadding;
    }


    public void setTagWhiteSpacePadding(int tagWhiteSpacePadding) {
        this.tagWhiteSpacePadding = tagWhiteSpacePadding;
    }

    public void setOriginalTextSizeDimenResource(int dimenResource) {
        setOriginalTextSize(dimenResource == 0 ? 0 :
                context.getResources().getDimensionPixelSize(dimenResource));
    }

    public void setOriginalTextSize(int originalTextSize) {
        this.originalTextSize = originalTextSize;
    }


    public void setCornerRadius(int cornerRadius) {
        this.cornerRadius = cornerRadius;
    }

    @Override
    public void updateDrawState(TextPaint ds) {
        super.updateDrawState(ds);
    }


    @Override
    public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y,
                     int bottom, @NonNull Paint paint) {
        // initialise values
        Bitmap bitmap = getCachedIconBitmap();
        float bitmapWidthWithPadding = 0;
        float bitmapTop = 0;
        float bitmapStart = 0;

        if (getCachedIconBitmap() != null) {
            bitmapWidthWithPadding = bitmap.getWidth() + iconTextSpacePadding;
            bitmapTop = y - (getCachedIconBitmap().getHeight() / 2) - tagTextBoundHeight / 2;
            bitmapStart = x + tagTextHorizontalPadding + tagWhiteSpacePadding;
        }

        float rectBottom = (y + tagTextVerticalPadding);
        float rectTop = (y - tagTextVerticalPadding) - tagTextBoundHeight;
        float rectStart = x + tagWhiteSpacePadding;
        float rectEnd = x + measureText(paint, text, start, end) + bitmapWidthWithPadding +
                (tagTextHorizontalPadding + tagTextHorizontalPadding) + tagWhiteSpacePadding;

        float textBottom = y;
        float textStart = x + bitmapWidthWithPadding + tagTextHorizontalPadding + tagWhiteSpacePadding;

        // draw canvas,
        // note: order is important
        paint.setTextSize(tagTextSize);

        RectF rect = new RectF(rectStart, rectTop, rectEnd, rectBottom);
        canvas.drawRoundRect(rect, cornerRadius, cornerRadius, mBgPaint);

        if (bitmap != null) {
            canvas.drawBitmap(bitmap, bitmapStart, bitmapTop, paint);
        }

        paint.setColor(textColor);
        canvas.drawText(text.subSequence(start, end).toString(), textStart, textBottom, paint);
    }

    @Nullable
    private Bitmap getCachedIconBitmap() {
        if (leftIconDrawableResource > 0) {
            WeakReference<Bitmap> wr = leftIconBitmapRef;
            Bitmap bitmap = null;

            if (wr != null)
                bitmap = wr.get();

            if (bitmap == null) {
                bitmap = drawableToBitmap(getCachedDrawable());
                leftIconBitmapRef = new WeakReference<>(bitmap);
            }
            return bitmap;
        }
        return null;
    }

    @Nullable
    private Drawable getCachedDrawable() {

        if (leftIconDrawableResource > 0) {
            WeakReference<Drawable> wr = drawableRef;
            Drawable drawable = null;

            if (wr != null)
                drawable = wr.get();

            if (drawable == null) {
                drawable = ResourcesCompat.getDrawable(context.getResources(), leftIconDrawableResource, null);
                drawableRef = new WeakReference<>(drawable);
            }

            return drawable;
        }


        return null;
    }


    @Nullable
    private Bitmap drawableToBitmap(Drawable drawable) {
        if (drawable == null) {
            return null;
        }
        Bitmap bitmap;

        if (drawable instanceof BitmapDrawable) {
            BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
            if (bitmapDrawable.getBitmap() != null) {
                return bitmapDrawable.getBitmap();
            }
        }

        if (drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) {
            bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
        } else {
            bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
        }

        Canvas canvas = new Canvas(bitmap);
        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
        drawable.draw(canvas);
        return bitmap;
    }

    @Override
    public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
        paint.setTextSize(tagTextSize);
        int bitmapWidthWithPadding = getCachedIconBitmap() == null ? 0 : (getCachedIconBitmap().getWidth() + iconTextSpacePadding);
        return Math.round(paint.measureText(text, start, end) +
                bitmapWidthWithPadding +
                (tagTextHorizontalPadding + tagTextHorizontalPadding) +
                (tagWhiteSpacePadding + tagWhiteSpacePadding));
    }

    private float measureText(Paint paint, CharSequence text, int start, int end) {
        paint.setTextSize(tagTextSize);
        return paint.measureText(text, start, end);
    }

    private int measureTextHeight(Paint paint, String text) {
        Rect result = new Rect();
        paint.getTextBounds(text, 0, text.length(), result);
        return result.height();
    }

    private int measureTextBoundHeight(int originalTextSize) {
        // note might need to set type face if original and tag typeface different
        Paint paint = new Paint();
        paint.setTextSize(originalTextSize);
        return measureTextHeight(paint, "T");
    }

    /* initialised measurements to determine if we need to override height of span in chooseHeight()
     * if tag will be longer than original font's top/bottom, we will calculate the difference in
     * height of tag and original text height.
     */
    private void measureChooseHeightRequired() {
        Paint originalSizePaint = new Paint();
        originalSizePaint.setTextSize(originalTextSize);

        Paint tagSizePaint = new Paint();
        tagSizePaint.setTextSize(tagTextSize);

        originalFontMetrics = originalSizePaint.getFontMetrics();
        Paint.FontMetrics tagFontMetrics = tagSizePaint.getFontMetrics();

        chooseHeightAddTop = tagTextVerticalPadding - ((int) -(originalFontMetrics.top - tagFontMetrics.top));
        chooseHeightAddBottom = tagTextVerticalPadding - ((int) (originalFontMetrics.bottom - tagFontMetrics.bottom));
    }

    // this method is called to measure height of span
    @Override
    public void chooseHeight(CharSequence text, int start, int end, int spanStartV, int v, Paint.FontMetricsInt fm) {
        if (chooseHeightAddTop > 0) {
            fm.top = (int) originalFontMetrics.top - chooseHeightAddTop;
            fm.ascent = (int) originalFontMetrics.ascent - chooseHeightAddTop;
        }
        if (chooseHeightAddBottom > 0) {
            fm.bottom = (int) originalFontMetrics.bottom + chooseHeightAddBottom;
            fm.descent = (int) originalFontMetrics.descent + chooseHeightAddBottom;
        }
    }
}